/**
 *
 */
package ywu.tools.timesync;

import java.io.*;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.util.Date;
import java.text.SimpleDateFormat;

import ywu.tools.timesync.ntp.NtpRequest;
import ywu.tools.timesync.ntp.NtpResponse;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.SystemClock;
import android.util.Log;

/**
 * @author ywu
 */
public class TimeSyncService extends Service
{
    private static final String DEF_SERVER = "wwv.nist.gov";
    private static final int SOCK_TIMEOUT = 3000;
    private static final int PORT_NTP = 123;
    private static final String TAG = "ywu.TimeSyncService";
    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyyMMdd.HHmmss");

    /*
     * (non-Javadoc)
     * @see android.app.Service#onBind(android.content.Intent)
     */
    @Override
    public IBinder onBind(final Intent intent)
    {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public void onCreate()
    {
        Log.i(TAG, "Starting " + TAG);
        syncSysTime(DEF_SERVER);
    }

    public Date syncSysTime(final String server)
    {
        Date newSysTime = null;
        DatagramChannel udpChnl = null;

        try
        {
            udpChnl = DatagramChannel.open();
            DatagramSocket socket = udpChnl.socket();
            socket.setSoTimeout(SOCK_TIMEOUT);
            socket.bind(new InetSocketAddress(0));

            NtpRequest req = new NtpRequest();
            int bytes = udpChnl.send(req.toByteBuffer(), new InetSocketAddress(server, PORT_NTP)); //time.jmu.edu.cn wwv.nist.gov

            Log.d(TAG, bytes + " bytes sent");

            socket.setSoTimeout(SOCK_TIMEOUT);
            ByteBuffer buf = ByteBuffer.allocate(60);
            udpChnl.receive(buf);

            NtpResponse resp = new NtpResponse();
            resp.fromByteBuffer(buf);
            long newTs = resp.calcSystemTime(req.getOriginateTimestamp());
            newSysTime = new Date(newTs);

            updateSysTime(newSysTime);
        }
        catch (Exception e)
        {
            Log.e(TAG, "close UDP channel failed", e);
        }
        finally
        {
            if (udpChnl != null)
            {
                try
                {
                    udpChnl.close();
                }
                catch (IOException e)
                {
                    Log.e(TAG, "close UDP channel failed", e);
                }
            }
        }

        return newSysTime;
    }

    /*
     * update system time with root privilege
     */
    private void updateSysTime(Date ts) {
        sudo("date -s " + DATE_FORMAT.format(ts));
    }

    private static void sudo(final String cmd) {
        Process p = null;

        try {
            p = sudoImpl(cmd);
        } catch (Throwable e) {
            throw new RuntimeException("failed to sudo: " + cmd, e);
        } finally {
            if (p != null) {
                p.destroy();
            }
        }
    }

    private static Process sudoImpl(final String cmd) throws Exception {
        Process p = new ProcessBuilder("su").redirectErrorStream(true).start();

        DataOutputStream os = new DataOutputStream(p.getOutputStream());
        os.writeBytes(cmd);

        // Close the terminal
        os.writeBytes("\nexit\n");
        os.flush();
        p.waitFor();

        int r = p.exitValue();
        String msg = readPrompt(p);
        Log.d(TAG, "sudo result=" + r + ", msg=" + msg);

        if (r != 0) {
            throw new RuntimeException("command '" + cmd + "' exit with error, return code is: " + r);
        }
        return p;
    }

    private static String readPrompt(final Process p) throws IOException {
        InputStream is = p.getInputStream();
        byte[] buf = new byte[128];
        int len;
        StringBuilder prompt = new StringBuilder();

        while ((len = is.read(buf)) > 0) {
            byte[] bytes = new byte[len];
            System.arraycopy(buf, 0, bytes, 0, len);
            prompt.append(new String(bytes));
        }
        return prompt.toString();
    }
}
